//  Copyright Fernando Pelliccioni 2011. Use, modification and
//  distribution is subject to the Boost Software License, Version
//  1.0. (See accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)
//
//
// For more information, see http://www.boost.org/libs/range/
//

#ifndef BOOST_ITERATOR_CONVERTER_BASE64_HPP_INCLUDED
#define BOOST_ITERATOR_CONVERTER_BASE64_HPP_INCLUDED

#include <boost/iterator/convert_iterator.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/adaptor/argument_fwd.hpp>
//#include <boost/range/algorithm_ext/copy_n.hpp>

#include <boost/type_traits/make_unsigned.hpp>

//TODO: incluir estos cambios en el codigo de Mathias

namespace boost {


//TODO: namespace range_detail

struct base64_traits
{
	static char encode(unsigned char n)
	{
		const char table[] =
			"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
			"abcdefghijklmnopqrstuvwxyz"
			"0123456789+/";

		return table[n];
	}

	static unsigned char decode(char c)
	{
		// TODO: should support non-ASCII
		const unsigned char table[] =
		{
			0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
			0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

			0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
			0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

			0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
			0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF, 0xFF, 0x3F,

			0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
			0x3C, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

			0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
			0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,

			0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
			0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

			0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
			0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,

			0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,
			0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
		};

		unsigned char uc = static_cast<unsigned char>(c);

		if (static_cast<std::size_t>(uc) >= sizeof(table))
		{
			//throw BOOST_IOSTREAMS_FAILURE("bad Base64 sequence");
			throw std::runtime_error("bad Base64 sequence");
		}

		unsigned char val = table[uc];
		
		if (val == 0xFF)
		{
			//throw BOOST_IOSTREAMS_FAILURE("bad Base64 sequence");
			throw std::runtime_error("bad Base64 sequence");
		}

		return val;
	}



	static char get_padding_char()
	{
		return padding;
	}

	
	static bool is_padding_char( char c )
	{
		return ( c == padding );
	}

private:
	static const char padding = '=';
};


//TODO: ver los caracteres que se usan. Hay secuencias de caracteres distintos para distintos usos, como URL
//TODO: ver de hacer TMP
template <typename Traits>
struct basic_base64_encoder
{
    typedef char input_type;
    typedef char output_type;
	typedef boost::make_unsigned<input_type>::type unsigned_input_type;			//TODO: ver typename
	typedef boost::make_unsigned<output_type>::type unsigned_output_type;
    typedef boost::mpl::int_<4> max_output;

    template <typename T>
    inline typename boost::make_unsigned<T>::type unsigned_cast( T val )			//explicit inline 
    {
		return static_cast<boost::make_unsigned<T>::type>(val);				//TODO: ver typename
	}

    
    template <typename InputIt, typename OutputIt>
    OutputIt ltr( InputIt & begin, InputIt end, OutputIt out )
    {
        //input_type in[3];
		unsigned_input_type in0;
		unsigned_input_type in1;
		unsigned_input_type in2;
        
        in0 = *begin++;
        
		*out++ = Traits::encode( in0 >> 2 );

        if (begin == end)
        {
			*out++ = Traits::encode( (in0 & 0x03) << 4 );
            *out++ = Traits::get_padding_char();
            *out++ = Traits::get_padding_char();
            return out;
        }
        
        in1 = *begin++;
		*out++ = Traits::encode( ((in0 & 0x03) << 4) | (in1 >> 4) );

        if (begin == end)
        {
			*out++ = Traits::encode( (in1 & 0x0F) << 2 );
            *out++ = Traits::get_padding_char();
            return out;
        }
        
        in2 = *begin++;
		*out++ = Traits::encode( ((in1 & 0x0F) << 2) | (in2 >> 6) );
		*out++ = Traits::encode( in2 & 0x3f );

        return out;
    }
    
    template<typename InputIt, typename OutputIt>
    OutputIt rtl( InputIt begin, InputIt & end, OutputIt out )
    {
        size_t to_read = std::distance(begin, end) % 3;
        if(!to_read)
		{
            to_read = 3;
		}
        
        input_type in[3];
        for(size_t i=0; i<to_read; i++)
		{
            in[to_read-i-1] = *--end;
		}
            
        input_type* b = in;
        return ltr(b, in+to_read, out);
    }
};



template <typename Traits>
struct basic_base64_decoder
{
    typedef char input_type;
    typedef char output_type;
    
    typedef boost::mpl::int_<4> max_output;
    
    template <typename InputIt, typename OutputIt>
    OutputIt ltr( InputIt & begin, InputIt end, OutputIt out )
    {
		input_type in[4] = {0,0,0,0};

        in[0] = Traits::decode( *begin++ );

        if ( begin == end ) // we need at least two input bytes for first byte output
		{
			throw std::runtime_error("bad Base64 sequence");
		}

		in[1] = Traits::decode( *begin++ );

        *out++ = ((in[0] << 2) | ((in[1] >> 4) & 0x3));

        if ( begin != end )
		{
			//char c = input_ptr[i];
			//InputIt::value_type c = *begin++;
			input_type c = *begin++;
			
            //if (c == '=')		
			if ( Traits::is_padding_char(c) )	// padding , end of input
			{ 
                BOOST_ASSERT( (in[1] & 0x0F)==0);
                return out;
            }
            
			//in[2] = decoding_data[static_cast<int>(input_ptr[i])];
			in[2] = Traits::decode( c );

            *out++ = ((in[1] << 4) & 0xF0) | ((in[2] >> 2) & 0x0F);
        }

        if ( begin != end )
		{
            //char c = input_ptr[i];
			//InputIt::value_type c = *begin++;
			input_type c = *begin++;

            //if ( c =='=' )			// padding , end of input
			if ( Traits::is_padding_char(c) )	// padding , end of input
			{ 
                BOOST_ASSERT( (in[2] & 0x03)==0);
                return out;
            }
            //in[3] = decoding_data[static_cast<int>(input_ptr[i])];
			in[3] = Traits::decode( c );

            *out++ = (((in[2] << 6) & 0xc0) | in[3] );
        }

		return out;
    }
    
    template<typename InputIt, typename OutputIt>
    OutputIt rtl(InputIt begin, InputIt & end, OutputIt out)
    {
		//TODO...
        size_t to_read = std::distance(begin, end) % 3;
        if(!to_read)
		{
            to_read = 3;
		}
        
        input_type in[3];
        for(size_t i=0; i<to_read; i++)
		{
            in[to_read-i-1] = *--end;
		}
            
        input_type* b = in;
        return ltr(b, in+to_read, out);
    }
};


typedef basic_base64_encoder<base64_traits> base64_encoder;
typedef basic_base64_decoder<base64_traits> base64_decoder;

//typedef basic_base64_encoder<urlsafe_base64_traits> urlsafe_base64_encoder;
//typedef basic_base64_decoder<urlsafe_base64_traits> urlsafe_base64_decoder;


template<class Converter>
class convert_holder : public boost::range_detail::holder<Converter>
{
public:
    explicit convert_holder(Converter value) : boost::range_detail::holder<Converter>(value) {}
};

template<class Rng, class Converter>
inline boost::converted_range<Rng, Converter>
operator|( Rng & range, convert_holder<Converter> const& holder )
{
    return boost::converted_range<Rng, Converter>(
        boost::make_convert_iterator(boost::begin(range), boost::end(range), boost::begin(range), holder.val ),
        boost::make_convert_iterator(boost::begin(range), boost::end(range), boost::end(range), holder.val )
    );
}

//template<class Rng, class Converter>
//inline boost::converted_range<const Rng, const Converter>
//operator|(  Rng const& rng, convert_holder<Converter> const& holder )
//{
//    return boost::converted_range<const Rng, Converter>(
//        boost::make_convert_iterator(boost::begin(range), boost::end(range), boost::begin(range), holder.val ),
//        boost::make_convert_iterator(boost::begin(range), boost::end(range), boost::end(range), holder.val )
//    );
//}

namespace adaptors
{

	namespace
	{
		const boost::range_detail::forwarder<convert_holder>
		converted = boost::range_detail::forwarder<convert_holder>();
	}

} //namespace adaptors

} //namespace boost

#endif //BOOST_ITERATOR_CONVERTER_BASE64_HPP_INCLUDED